#include "umlclassitem.h"

UmlClassItem::UmlClassItem(UmlProject *project) : UmlRelationalItem(project)
{
    this->_itemTypeFlags |= UmlItem::UmlClassItemType;

    this->_generalizationPorts[UmlRelation::NORTH] = 0;
    this->_generalizationPorts[UmlRelation::SOUTH] = 0;
    this->_generalizationPorts[UmlRelation::WEST] = 0;
    this->_generalizationPorts[UmlRelation::EAST] = 0;

    this->_realizationPorts[UmlRelation::NORTH] = 0;
    this->_realizationPorts[UmlRelation::SOUTH] = 0;
    this->_realizationPorts[UmlRelation::WEST] = 0;
    this->_realizationPorts[UmlRelation::EAST] = 0;

    this->_isAbstract = false;
    this->_isInterface = false;
    this->_itemUnderMouse = 0;

    this->setHandlesChildEvents(true);
    this->setAcceptHoverEvents(true);

    // init pens
    this->_whitePen.setColor(Qt::white);

    // header item
    this->_headerGraphicsItem.setParentItem(this);
    this->_headerGraphicsItem.setClassItem(this);

    this->_headerAttBorderItem.setParentItem(this);
    this->_headerAttBorderItem.setPen(this->_borderPen);

    this->_attMethBorderItem.setParentItem(this);
    this->_attMethBorderItem.setPen(this->_borderPen);

    // setting buttons
    this->appendButton(new AssociationButton());
    this->appendButton(new InheritanceButton());
    this->appendButton(new ImplementationButton());
    this->appendButton(new AggregationButton());
    this->appendButton(new KompositionButton());

    // show
    this->_showAtts = this->_showMeths = true;

}

UmlClassItem::~UmlClassItem()
{
    qDebug() << "deleting umlClassItem: " << this->_name.toLocal8Bit();

    while (!this->_attList.isEmpty()) delete this->_attList.takeFirst();

    while (!this->_methList.isEmpty()) delete this->_methList.takeFirst();
}

void UmlClassItem::setText(QString name)
{
    UmlItem::setText(name);

    this->_headerGraphicsItem.setText(name);

    this->updateBoundingBox();
    this->update();
}

QRectF UmlClassItem::nameRect() const
{
    return this->_umlTextRect;
}

void UmlClassItem::appendAtt(ClassAttribute *att)
{
    if (this->hasAttribute(att)) {
        return;
    }

    att->setClassItem(this);
    att->setProject(this->_project);
    this->_attList.append(att);
    this->addToGroup(att);
    this->updateBoundingBox();
}

QList<ClassAttribute*> UmlClassItem::attList()
{
    return this->_attList;
}

void UmlClassItem::removeAtt(ClassAttribute *att)
{
    if (!this->hasAttribute(att)) {
        return;
    }

    this->_attList.removeAll(att);
    this->removeFromGroup(att);

    delete att;

    this->update();
}

bool UmlClassItem::hasAttribute(ClassAttribute *att)
{
    return this->_attList.contains(att);
}

void UmlClassItem::appendMeth(ClassMethod *meth)
{
    if (this->hasMethod(meth)) {
        return;
    }

    meth->setClassItem(this);
    meth->setProject(this->project());
    this->_methList.append(meth);
    this->addToGroup(meth);
    this->updateBoundingBox();
}

QList<ClassMethod*> UmlClassItem::methList()
{
    return this->_methList;
}

bool UmlClassItem::hasMethod(ClassMethod *meth)
{
    return this->_methList.contains(meth);
}

void UmlClassItem::removeMeth(ClassMethod *meth)
{
    if (!this->hasMethod(meth)) {
        return;
    }

    this->_methList.removeAll(meth);
    this->removeFromGroup(meth);

    delete meth;

    this->update();
}

bool UmlClassItem::isInterface()
{
    return this->_isInterface;
}

void UmlClassItem::setIsInterface(bool isInterface)
{
    this->_isInterface = isInterface;
    this->_isAbstract = false;

    this->_headerGraphicsItem.setClassItem(this);
    this->updateBoundingBox();
    this->update();
}

bool UmlClassItem::isAbstract()
{
    return this->_isAbstract;
}

void UmlClassItem::setIsAbstract(bool isAbstract)
{
    this->_isAbstract = isAbstract;
    this->_isInterface = false;

    this->_headerGraphicsItem.setClassItem(this);
    this->updateBoundingBox();
    this->update();
}

bool UmlClassItem::isClass()
{
    return !this->isAbstract() && !this->isInterface();
}

bool UmlClassItem::showAtts()
{
    return this->_showAtts;
}

void UmlClassItem::setShowAtts(bool show)
{
    this->_showAtts = show;
    this->updateBoundingBox();
    this->update();
}

bool UmlClassItem::showMeths()
{
    return this->_showMeths;
}

void UmlClassItem::setShowMeths(bool show)
{
    this->_showMeths = show;
    this->updateBoundingBox();
    this->update();
}

bool UmlClassItem::hasAttributes()
{
    return !this->_attList.isEmpty();
}

bool UmlClassItem::hasMethods()
{
    return !this->_methList.isEmpty();
}

void UmlClassItem::appendRelation(UmlRelation *relation, UmlRelation::RelativeItemPosition direction)
{
    if (relation->relationIsOfType(UmlRelation::UmlGeneralizationRelationType) && relation->item2() == this) {
        if (!this->_generalizationPorts[direction]) {
            this->_generalizationPorts[direction] = new RelationPort();
        }
        this->_portList[direction][relation] = this->_generalizationPorts[direction];
        this->addToGroup(this->_portList[direction].value(relation));
        this->drawPorts(direction);
    }

    else if (relation->relationIsOfType(UmlRelation::UmlRealizationRelationType) && relation->item2() == this) {
        if (!this->_realizationPorts[direction]) {
            this->_realizationPorts[direction] = new RelationPort();
        }
        this->_portList[direction][relation] = this->_realizationPorts[direction];
        this->addToGroup(this->_portList[direction].value(relation));
        this->drawPorts(direction);
    }

    else {
        UmlRelationalItem::appendRelation(relation, direction);
    }
}

void UmlClassItem::removeRelation(UmlRelation *relation, UmlRelation::RelativeItemPosition direction)
{
    UmlRelationalItem::removeRelation(relation, direction);

    if (relation->relationIsOfType(UmlRelation::UmlGeneralizationRelationType) &&
        this->_portList[direction].keys(this->_generalizationPorts[direction]).isEmpty()) {
        this->_generalizationPorts[direction] = 0;
    }
    else if (relation->relationIsOfType(UmlRelation::UmlRealizationRelationType) &&
             this->_portList[direction].keys(this->_realizationPorts[direction]).isEmpty() &&
             !this->_portList[direction].values().contains(this->_realizationPorts[direction])) {
        this->_realizationPorts[direction] = 0;
    }
}

QList<UmlRelation*> UmlClassItem::parentClasses()
{
    QList<UmlRelation*> list;

    foreach (UmlRelation *relation, this->relations(UmlRelation::UmlGeneralizationRelationType)) {
        if (relation->item1() == this) {
            list.append(relation);
        }
    }

    return list;
}

QList<UmlRelation*> UmlClassItem::realizedInterfaces()
{
    QList<UmlRelation*> list;

    foreach (UmlRelation *relation, this->relations(UmlRelation::UmlRealizationRelationType)) {
        if (relation->item1() == this) {
            list.append(relation);
        }
    }

    return list;
}

QList<UmlRelation*> UmlClassItem::allAssociations()
{
    QList<UmlRelation*> list;

    foreach (UmlRelation *relation, this->relations(UmlRelation::UmlAssociationRelationType | UmlRelation::UmlAggregationRelationType | UmlRelation::UmlCompositionRelationType)) {
        list.append(relation);
    }

    return list;
}

bool UmlClassItem::checkRelation(UmlRelation *possibleRelation, UmlRelationalItem *possibleRelationPartner)
{
    bool ok = false;
    UmlClassItem *classItem = 0;

    if (!possibleRelation || !possibleRelationPartner) {
        this->setGraphicsEffect(0);
        return false;
    }

    if (!(classItem = dynamic_cast<UmlClassItem*>(possibleRelationPartner))) {
        this->setGraphicsEffect(0);
        return false;
    }

    // Assoziationen, Kompositionen und Aggregationen gehen immer
    if (possibleRelation->relationIsOfType(UmlRelation::UmlUndirectedRelationType)) {
        ok =  true;
    } else {

        if (possibleRelation->relationIsOfType(UmlRelation::UmlGeneralizationRelationType)) {
            ok = (this->isClass() && classItem->isClass()) ||
                    (this->isClass() && classItem->isAbstract()) ||
                    (this->isAbstract() && classItem->isClass()) ||
                    (this->isAbstract() && classItem->isAbstract()) ||
                    (this->isInterface() && classItem->isInterface());
        }

        if (possibleRelation->relationIsOfType(UmlRelation::UmlRealizationRelationType)) {
            ok = (this->isInterface() && classItem->isClass()) ||
                    (this->isInterface() && classItem->isAbstract());
        }
    }

    if (ok) {
        this->setGraphicsEffect(this->acceptDropEffect());
    } else{
        this->setGraphicsEffect(this->denyDropEffect());
    }


    return ok;
}

/*!
 * ==================== PAINTING - METHODS ====================
 */

void UmlClassItem::updateBoundingBox()
{
    qreal penWidth = this->_borderItem.pen().widthF();
    qreal width = qRound(this->minWidth());
    qreal yOffset = penWidth;

    this->_headerGraphicsItem.setWidth(width);
    this->_headerGraphicsItem.setPos(penWidth, yOffset);

    yOffset += this->_headerGraphicsItem.boundingRect().height();

    // attributes
    this->_headerAttBorderItem.setVisible(this->_showAtts);
    this->_headerAttBorderItem.setPos(penWidth, yOffset);
    this->_headerAttBorderItem.setLine(0, 0, width, 0);
    this->_attRect.setTopLeft(QPointF(0, yOffset));
    if (!this->_attList.isEmpty()) {
        foreach(ClassAttribute *att, this->_attList) {
            att->setVisible(this->_showAtts);
            att->setPos(penWidth, yOffset);
            att->setWidth(width);
            yOffset += this->_showAtts ? att->boundingRect().height() : 0;
        }
    } else {
        yOffset += this->_showAtts ? 20 : 0;
    }
    this->_attRect.setBottomRight(QPointF(width, yOffset));

    // methods
    this->_attMethBorderItem.setVisible(this->_showMeths);
    this->_attMethBorderItem.setPos(0, yOffset);
    this->_attMethBorderItem.setLine(0, 0, width, 0);
    this->_methRect.setTopLeft(QPointF(0, yOffset));
    if (!this->_methList.isEmpty()) {
        foreach(ClassMethod *meth, this->_methList) {
            meth->setVisible(this->_showMeths);
            meth->setPos(penWidth, yOffset);
            meth->setWidth(width);
            yOffset += this->_showMeths ? meth->boundingRect().height() : 0;
        }
    } else {
        yOffset += this->_showMeths ? 20 : 0;
    }
    this->_methRect.setBottomRight(QPointF(width, yOffset));

    this->prepareGeometryChange();
    this->_boundingRect.setTopLeft(QPointF(0, 0));
    this->_boundingRect.setWidth(width + penWidth);
    this->_boundingRect.setHeight(yOffset);

    this->_borderItem.setPolygon(QPolygonF(this->_boundingRect));

    this->_boundingRect = this->_borderItem.boundingRect();

    this->drawPorts(UmlRelation::NORTH);
    this->drawPorts(UmlRelation::SOUTH);
    this->drawPorts(UmlRelation::WEST);
    this->drawPorts(UmlRelation::EAST);

    this->updateButtonPos();

    emit this->boundingRectHasChanged();
}

QVariant UmlClassItem::itemChange(GraphicsItemChange change, const QVariant &value)
{
    if (change == QGraphicsItem::ItemSceneHasChanged) {

        this->_headerGraphicsItem.setParentItem(this);
        this->addToGroup(&this->_headerGraphicsItem);

        this->_headerGraphicsItem.setParentItem(this);
        this->addToGroup(&this->_headerAttBorderItem);

        this->_attMethBorderItem.setParentItem(this);
        this->addToGroup(&this->_attMethBorderItem);

        foreach(ClassAttribute *att, this->_attList) {
            this->addToGroup(att);
        }

        foreach(ClassMethod *meth, this->_methList) {
            meth->setParentItem(this);
            this->addToGroup(meth);
        }

        foreach (UmlRelation::RelativeItemPosition direction, this->_generalizationPorts.keys()) {
            if (this->_generalizationPorts[direction]) {
                this->_generalizationPorts[direction]->setParentItem(this);
                this->addToGroup(this->_generalizationPorts[direction]);
            }
        }

        foreach (UmlRelation::RelativeItemPosition direction, this->_realizationPorts.keys()) {
            if (this->_realizationPorts[direction]) {
                this->_realizationPorts[direction]->setParentItem(this);
                this->addToGroup(this->_realizationPorts[direction]);
            }
        }

        this->updateBoundingBox();
    }

    return UmlRelationalItem::itemChange(change, value);
}

QList<UmlClassItem*> UmlClassItem::implements()
{
    QList<UmlClassItem*> list;

    /*foreach (UmlRelation *relation, this->interfaceRelations()) {
        list.append(static_cast<UmlClassItem*>(relation->parentItem()));
    }*/

    return list;
}

QList<UmlClassItem*> UmlClassItem::inherits()
{
    QList<UmlClassItem*> list;

    /*foreach (UmlRelation *relation, this->inheritancesRelations()) {
        list.append(static_cast<UmlClassItem*>(relation->parentItem()));
    }*/

    return list;
}

void UmlClassItem::hoverEnterEvent(QGraphicsSceneHoverEvent *event)
{
    this->_itemUnderMouse = this->findItemUnderMouse(event->pos());

    if (this->_itemUnderMouse) {
        this->_itemUnderMouse->setIsHovered(true);
    }

    UmlRelationalItem::hoverEnterEvent(event);
}

void UmlClassItem::hoverMoveEvent(QGraphicsSceneHoverEvent *event)
{
    UmlTextChildItem *currentItem = this->findItemUnderMouse(event->pos());

    if (this->_itemUnderMouse) {
        this->_itemUnderMouse->setIsHovered(false);
    }

    this->_itemUnderMouse = currentItem;

    if (this->_itemUnderMouse) {
        this->_itemUnderMouse->setIsHovered(true);
    }

    UmlRelationalItem::hoverMoveEvent(event);
}

void UmlClassItem::hoverLeaveEvent(QGraphicsSceneHoverEvent *event)
{
    if (this->_itemUnderMouse) {
        this->_itemUnderMouse->setIsHovered(false);
        this->_itemUnderMouse = 0;
    }

    UmlRelationalItem::hoverLeaveEvent(event);
}

void UmlClassItem::mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event)
{   
    if (this->_itemUnderMouse && event->button() == Qt::LeftButton) {
        this->_itemUnderMouse->openEditor();
    }
}

QList<QAction*> UmlClassItem::contextActions()
{
    QList<QAction*> actions = UmlRelationalItem::contextActions();

    QAction *separator1 = new QAction(this);
    separator1->setSeparator(true);
    actions.prepend(separator1);

    QAction *abstractAction = new QAction(tr("is Abstract"), this);
    abstractAction->setCheckable(true);
    abstractAction->setChecked(this->_isAbstract);
    actions.prepend(abstractAction);
    QObject::connect(abstractAction, SIGNAL(triggered()), this, SLOT(toggleIsAbstract()));

    QAction *interfaceAction = new QAction(tr("is Interface"), this);
    interfaceAction->setCheckable(true);
    interfaceAction->setChecked(this->_isInterface);
    actions.prepend(interfaceAction);
    QObject::connect(interfaceAction, SIGNAL(triggered()), this, SLOT(toggleIsInterface()));

    QAction *classAction = new QAction(tr("is Class"), this);
    classAction->setCheckable(true);
    classAction->setChecked(!this->_isInterface && !this->_isAbstract);
    actions.prepend(classAction);
    QObject::connect(classAction, SIGNAL(triggered()), this, SLOT(toggleIsClass()));

    QAction *separator2 = new QAction(this);
    separator2->setSeparator(true);
    actions.prepend(separator2);

    QAction *showMeths = new QAction(tr("Show Methods"), this);
    showMeths->setIcon(QIcon(":icons/contextmenu/show.png"));
    showMeths->setCheckable(true);
    showMeths->setChecked(this->_showMeths);
    actions.prepend(showMeths);
    QObject::connect(showMeths, SIGNAL(triggered()), this, SLOT(toggleShowMeths()));


    QAction *showAtts = new QAction(tr("Show Attributes"), this);
    showAtts->setIcon(QIcon(":icons/contextmenu/show.png"));
    showAtts->setCheckable(true);
    showAtts->setChecked(this->_showAtts);
    actions.prepend(showAtts);
    QObject::connect(showAtts, SIGNAL(triggered()), this, SLOT(toggleShowAtts()));

    QAction *separator3 = new QAction(this);
    separator3->setSeparator(true);
    actions.prepend(separator3);

    QAction *newMethod = new QAction(tr("Add Method"), this);
    newMethod->setIcon(QIcon(":icons/contextmenu/add-attribute.png"));
    actions.prepend(newMethod);
    QObject::connect(newMethod, SIGNAL(triggered()), this, SLOT(openMethodEditor()));

    QAction *newAttribute = new QAction(tr("Add Attribute"), this);
    newAttribute->setIcon(QIcon(":icons/contextmenu/add-attribute.png"));
    actions.prepend(newAttribute);
    QObject::connect(newAttribute, SIGNAL(triggered()), this, SLOT(openAttributeEditor()));

    return actions;
}

qreal UmlClassItem::minWidth()
{
    // header width
    qreal width = this->_headerGraphicsItem.size().width();

    // attribute width
    foreach (ClassAttribute *att, this->_attList) {
        width = att->size().width() > width ? att->size().width() : width;
    }

    // method width
    foreach (ClassMethod *meth, this->_methList) {
        width = meth->size().width() > width ? meth->size().width() : width;
    }

    return width;
}

UmlTextChildItem* UmlClassItem::findItemUnderMouse(QPointF pos)
{

    if (!this->boundingRect().contains(pos)) {
        return 0;
    }

    if (this->_headerGraphicsItem.boundingRect().contains(pos)) {
        return &this->_headerGraphicsItem;
    }


    else if (this->_attRect.contains(pos)) {
        foreach (ClassAttribute *att, this->_attList) {
            QRectF rect = att->mapRectToParent(att->boundingRect());
            if (rect.contains(pos)) {
                return att;
            }
        }
    }

    else if (this->_methRect.contains(pos)) {
        foreach (ClassMethod *meth, this->_methList) {
            QRectF rect = meth->mapRectToParent(meth->boundingRect());
            if (rect.contains(pos)) {
                return meth;
            }
        }
    }

    return 0;
}

int UmlClassItem::countPorts(UmlRelation::RelativeItemPosition direction)
{
    int count = 0;

    if (this->_generalizationPorts[direction]) count += 1;
    if (this->_realizationPorts[direction]) count += 1;

    return count;
}

void UmlClassItem::openClassEditor()
{
    ClassEditor *editor = new ClassEditor();
    editor->setClassItem(this);

    if (editor->exec() == QDialog::Rejected) {
        this->updateBoundingBox();
        this->update();
    }
}

void UmlClassItem::openAttributeEditor(ClassAttribute *attribute)
{
    AttributeEditor *editor = new AttributeEditor(this, attribute);
    editor->setDatatypes(this->_project->dataTypes());

    if (editor->exec() == QDialog::Accepted) {
        this->updateBoundingBox();
        this->update();
    }
}

void UmlClassItem::openMethodEditor(ClassMethod *method)
{    
    MethodEditor *editor = new MethodEditor(this, method);
    editor->setDatatypes(this->_project->dataTypes());

    if (editor->exec() == QDialog::Accepted) {
        this->updateBoundingBox();
        this->update();
    }
}

void UmlClassItem::toggleIsClass()
{
    this->_isAbstract = false;
    this->_isInterface = false;
    this->_headerGraphicsItem.setClassItem(this);
    this->updateBoundingBox();
    this->update();
}

void UmlClassItem::toggleIsAbstract()
{
    this->_isAbstract = !this->_isAbstract;
    this->_isInterface = false;
    this->_headerGraphicsItem.setClassItem(this);
    this->updateBoundingBox();
    this->update();
}

void UmlClassItem::toggleIsInterface()
{
    this->_isInterface = !this->_isInterface;
    this->_isAbstract = false;
    this->_headerGraphicsItem.setClassItem(this);
    this->updateBoundingBox();
    this->update();
}

void UmlClassItem::toggleShowAtts()
{
    this->setShowAtts(!this->showAtts());
}

void UmlClassItem::toggleShowMeths()
{
    this->setShowMeths(!this->showMeths());
}
